iT邦幫忙

2026 iThome 鐵人賽

0
AI Engineering

Java × AI:30天打造智慧健康助理系列 第 13 篇

用 Java 打造每日飲水量紀錄功能

  • 分享至 

  • xImage
  •  

Day 13|用 Java 打造每日飲水量紀錄功能

昨天的 Day 12,我們成功讓智慧健康助理加入 BMI 分類功能。

現在系統不只能計算 BMI,還可以根據數值顯示對應的分類。

不過仔細想想,目前的功能都比較偏向「計算」。

如果想讓智慧健康助理真的幫忙管理日常生活,是不是應該開始加入紀錄功能?

所以今天決定挑戰一個新的功能:

每日飲水量紀錄!

這次不只是接收資料後回傳結果,而是要讓 Java 暫時記住使用者輸入的飲水量,並計算累積總量。

今天的目標是:

利用 Spring Boot、POST API 和 Java List,建立簡單的每日飲水量紀錄功能。


一、為什麼選擇飲水量紀錄?

一開始規劃智慧健康助理時,就希望它不只是 BMI 計算器,而是可以逐漸加入不同的健康管理功能。

例如:

  • BMI 計算
  • 飲水量紀錄
  • 睡眠時間紀錄
  • 運動紀錄
  • 健康資訊摘要

其中飲水量紀錄相對容易入門。

因為需要處理的資料很簡單:

飲水量(ml)

例如:

早上喝水 300 ml
中午喝水 500 ml
下午喝水 250 ml

系統就可以計算:

300 + 500 + 250 = 1050 ml

最後回傳:

{
  "totalWater": 1050
}

這樣就完成一個簡單的紀錄功能!


二、今天要完成哪些功能?

今天先規劃兩個 API。

HTTP Method API 功能
POST /health/water 新增飲水紀錄
GET /health/water 查詢當日累積飲水量

例如使用者傳送:

{
  "amount": 300
}

系統就會新增一筆 300 ml 的飲水紀錄。

接著再傳送:

{
  "amount": 500
}

系統就會累積成:

800 ml

不過今天還不會使用資料庫。

所以我們先利用 Java 的 List,將資料暫時儲存在記憶體裡。

這代表只要 Spring Boot 重新啟動,紀錄就會消失。

目前先把重點放在理解資料如何新增、儲存和計算。


三、認識 Java 的 List

今天會使用到 Java Collection Framework 裡面的:

List

List 可以用來儲存多筆資料。

例如:

List<Integer> waterRecords = new ArrayList<>();

這代表建立一個可以存放整數的 List。

接著可以加入資料:

waterRecords.add(300);
waterRecords.add(500);
waterRecords.add(250);

現在 List 裡面就有:

[300, 500, 250]

如果想取得所有資料,就可以使用迴圈:

for (int amount : waterRecords) {
    System.out.println(amount);
}

輸出:

300
500
250

所以今天我們就可以利用 List 來保存每一次的飲水量。


四、建立 WaterRequest DTO

前幾天已經學會使用 DTO 接收 JSON。

今天也使用相同的方式。

首先建立:

WaterRequest.java

程式如下:

package com.example.healthassistant.dto;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

public class WaterRequest {

    @NotNull(message = "Water amount is required.")
    @Positive(message = "Water amount must be greater than 0.")
    private Integer amount;

    public WaterRequest() {
    }

    public Integer getAmount() {
        return amount;
    }

    public void setAmount(Integer amount) {
        this.amount = amount;
    }
}

這裡使用了 Day 11 學過的:

@NotNull

以及:

@Positive

避免使用者沒有輸入飲水量,或輸入 0、負數。

例如:

{
  "amount": -300
}

就不應該被接受。

另外,這裡使用 Integer 而不是 int,也是為了讓 @NotNull 可以判斷使用者是否漏掉欄位。


五、建立 WaterService

接下來是今天最重要的部分。

我們要建立:

WaterService.java

負責管理飲水紀錄。

完整程式如下:

package com.example.healthassistant.service;

import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

@Service
public class WaterService {

    private final List<Integer> waterRecords = new ArrayList<>();

    private LocalDate recordDate = LocalDate.now();

    private void resetIfNewDay() {

        LocalDate today = LocalDate.now();

        if (!today.equals(recordDate)) {
            waterRecords.clear();
            recordDate = today;
        }
    }

    public synchronized void addWater(int amount) {

        if (amount <= 0) {
            throw new IllegalArgumentException(
                "Water amount must be greater than 0."
            );
        }

        resetIfNewDay();

        waterRecords.add(amount);
    }

    public synchronized int getTotalWater() {

        resetIfNewDay();

        int total = 0;

        for (int amount : waterRecords) {
            total += amount;
        }

        return total;
    }
}

這次的程式比之前稍微長一點,但其實可以拆成幾個部分理解。


六、第一個功能:新增飲水紀錄

先看這段:

private final List<Integer> waterRecords = new ArrayList<>();

它會建立一個 List,用來存放飲水量。

接著:

public synchronized void addWater(int amount) {

    if (amount <= 0) {
        throw new IllegalArgumentException(
            "Water amount must be greater than 0."
        );
    }

    resetIfNewDay();

    waterRecords.add(amount);
}

這個 Method 負責新增紀錄。

例如呼叫:

addWater(300);

List 就會變成:

[300]

再呼叫:

addWater(500);

就會變成:

[300, 500]

這樣就可以把多次飲水紀錄保存起來。

這裡還多了一個:

synchronized

因為 Spring 的 Service 通常會被多個 HTTP Request 共用,而 ArrayList 本身不是執行緒安全的。

所以這次先使用 synchronized,避免多個請求同時修改紀錄時產生基本的並行問題。

不過這仍然只是單機、單一使用者的練習版本,還不是正式的多使用者資料儲存設計。


七、第二個功能:計算累積飲水量

接下來是:

public synchronized int getTotalWater() {

    resetIfNewDay();

    int total = 0;

    for (int amount : waterRecords) {
        total += amount;
    }

    return total;
}

這裡使用 Java 的:

for

迴圈,將 List 裡面的資料加總。

例如:

waterRecords = [300, 500, 250]

計算過程:

total = 0

total = 0 + 300
total = 300 + 500
total = 800 + 250

最後:

total = 1050

因此 Method 就會回傳:

1050

這樣就完成累積飲水量計算!


八、第三個功能:跨日自動清空

既然是「每日飲水量」,就不能一直把昨天的飲水量累積到今天。

所以今天還加入一個簡單的日期判斷。

private LocalDate recordDate = LocalDate.now();

LocalDate 是 Java 用來表示日期的類別。

例如:

2026-10-17

接著建立:

private void resetIfNewDay() {

    LocalDate today = LocalDate.now();

    if (!today.equals(recordDate)) {
        waterRecords.clear();
        recordDate = today;
    }
}

它會檢查今天的日期是否和上一次紀錄的日期相同。

如果日期不同,就執行:

waterRecords.clear();

清空舊的飲水紀錄。

例如:

10/17
飲水總量:1500 ml

到了:

10/18

下一次新增或查詢時,就會先清空前一天的資料。

不過這種做法只保留當日紀錄,無法查詢歷史資料。

而且日期是依照伺服器的時區判斷,未來如果系統有不同地區的使用者,就需要進一步處理時區問題。


九、建立 WaterController

Service 完成之後,接下來要建立 API。

建立:

WaterController.java

完整程式如下:

package com.example.healthassistant.controller;

import com.example.healthassistant.dto.WaterRequest;
import com.example.healthassistant.service.WaterService;

import jakarta.validation.Valid;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/health/water")
public class WaterController {

    private final WaterService waterService;

    public WaterController(WaterService waterService) {
        this.waterService = waterService;
    }

    @PostMapping
    public ResponseEntity<Map<String, Object>> addWater(
            @Valid @RequestBody WaterRequest request) {

        waterService.addWater(request.getAmount());

        Map<String, Object> result = new HashMap<>();

        result.put("message", "Water record added successfully.");
        result.put("amount", request.getAmount());
        result.put("totalWater", waterService.getTotalWater());

        return ResponseEntity.ok(result);
    }

    @GetMapping
    public ResponseEntity<Map<String, Object>> getWater() {

        Map<String, Object> result = new HashMap<>();

        result.put("totalWater", waterService.getTotalWater());
        result.put("unit", "ml");

        return ResponseEntity.ok(result);
    }
}

這裡出現一個新的 Annotation:

@RequestMapping("/health/water")

它可以設定整個 Controller 共用的路徑。

所以:

@PostMapping

對應:

POST /health/water

而:

@GetMapping

對應:

GET /health/water

這樣就不用在每個 Method 裡面重複寫完整路徑。


十、使用 Postman 新增飲水紀錄

接下來重新啟動 Spring Boot。

先測試新增資料。

HTTP Method:

POST

URL:

http://localhost:8080/health/water

Body:

{
  "amount": 300
}

預期回傳:

{
  "message": "Water record added successfully.",
  "amount": 300,
  "totalWater": 300
}

接著再送出第二次:

{
  "amount": 500
}

預期回傳:

{
  "message": "Water record added successfully.",
  "amount": 500,
  "totalWater": 800
}

這代表我們的 List 已經成功累積資料。


十一、使用 GET 查詢飲水量

接著測試第二個 API。

HTTP Method:

GET

URL:

http://localhost:8080/health/water

這次不需要傳送 Body。

如果剛剛新增了:

300 ml
500 ml

預期回傳:

{
  "totalWater": 800,
  "unit": "ml"
}

這樣就完成了兩個 API:

POST → 新增紀錄

GET → 查詢總量

也終於讓智慧健康助理開始具備基本的資料紀錄功能!


十二、目前的專案結構

今天新增了三個 Java Class。

目前的專案可以整理成:

health-assistant
│
├── src/main/java/com/example/healthassistant
│   │
│   ├── HealthAssistantApplication.java
│   │
│   ├── controller
│   │   ├── HealthController.java
│   │   └── WaterController.java
│   │
│   ├── service
│   │   ├── BmiService.java
│   │   └── WaterService.java
│   │
│   ├── dto
│   │   ├── HealthRequest.java
│   │   └── WaterRequest.java
│   │
│   └── exception
│       └── GlobalExceptionHandler.java
│
└── pom.xml

現在我們的系統已經有兩種不同的健康功能:

BMI 功能

HealthController
        ↓
BmiService
        ↓
計算與分類 BMI

飲水紀錄功能

WaterController
        ↓
WaterService
        ↓
List 儲存飲水量
        ↓
計算累積總量

這也讓我更能理解之前學習 Controller 與 Service 分層的重要性。

如果所有功能都寫在同一個 Controller 裡面,現在應該已經開始變得很混亂了 XD


十三、使用 List 有什麼限制?

雖然今天成功建立飲水量紀錄功能,但目前還有一些限制。

1. 重新啟動資料會消失

因為 List 是儲存在程式記憶體裡。

只要 Spring Boot 重新啟動,資料就會消失。

2. 沒辦法保存歷史紀錄

目前跨日就會清空資料。

所以無法查詢:

昨天喝了多少水?

或是:

上週平均飲水量是多少?

3. 所有使用者共用同一份資料

目前還沒有帳號系統。

因此如果不同使用者呼叫相同 API,飲水紀錄會被放在同一個 List 裡。

這顯然不適合真正的健康管理系統。

4. 不適合正式部署

目前的設計只適合用來練習。

如果未來希望保存多天紀


上一篇
使用 Java 加入 BMI 分類功能
下一篇
第一次認識資料庫與 H2
系列文
Java × AI:30天打造智慧健康助理 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言